#include "thread.h"
#include "fifo.h"
#include <stdio.h>
#include <stdlib.h>
#include <ucontext.h>
#include <valgrind/valgrind.h>
#include "macros.h"
#include <signal.h>
#include <sys/time.h>
#include <unistd.h>

#define SDELAY 0
#define USDELAY 50000

// Mettre thread_yield à la fin de thread_create (imp pour préemption)

struct thread * current =NULL;
struct thread * runq_head=NULL;
struct thread * runq_tail=NULL;
struct thread * waitq_head=NULL;
struct thread * waitq_tail=NULL;
int initialized_library = 0;
struct itimerval *timer;
struct sigaction sig;

/*
  struct itimerval {
  struct timeval it_interval; // valeur suivante 
  struct timeval it_value;    // valeur actuelle 
  };

  struct timeval {
  long tv_sec;                // secondes        
  long tv_usec;               // micro secondes

*/

void thread_disp(void) {
  struct thread *t = runq_head;
  printf("Current = %p (%d)\n############### RUNQ\n", current, current->status);
  while(t) {
    printf("%p (%d), ", t, t->status);
    t = t->next;
  }
  printf("\n############### FIN RUNQ\n\n");
}

void sig_handler (int signum)
{
  thread_yield();
}

extern void thread_init() {
  struct thread *t = malloc(sizeof(struct thread)); /* ici on initialise le main comme un thread classique pour avoir un current*/

  timer = calloc(1, sizeof(struct itimerval));
  getitimer(ITIMER_VIRTUAL, timer);
  timer->it_interval.tv_sec = SDELAY;
  timer->it_interval.tv_usec = USDELAY;
  timer->it_value.tv_sec = SDELAY;
  timer->it_value.tv_usec = USDELAY;
  setitimer(ITIMER_VIRTUAL, timer, NULL);
	  	

  sigemptyset (&sig.sa_mask);
  sig.sa_handler = sig_handler;
  sig.sa_flags = 0;
  sigaction(SIGALRM, &sig, NULL);
	  	
  getcontext(&t->ctx);
  t->waited_by=NULL;
  NEW_CURRENT(t)
    initialized_library=1;
}
	 
     
extern thread_t thread_self(void){
  //ne peut pas renvoyer NULL
  if(initialized_library!=1){
    struct thread * newthread = malloc(sizeof(struct thread));
    thread_init(newthread);
  }
  return current;
}

extern void  thread_call(void *(* func)(void *), void * funcarg){
  thread_exit(func(funcarg));
}

/* creer un nouveau thread qui va exécuter la fonction func avec l'argument funcarg.
 * renvoie 0 en cas de succès, -1 en cas d'erreur.
 */
extern int thread_create(thread_t *newthreadp, void *(*func)(void *), void *funcarg){
  struct thread * newthread =malloc(sizeof(struct thread));
  ucontext_t * uc = &newthread->ctx;
 
  getcontext(uc);
  uc->uc_stack.ss_size = 64*1024;
  uc->uc_stack.ss_sp = malloc(uc->uc_stack.ss_size); //ss_sp = pointeur de pile
  /* juste après l'allocation de la pile */
  newthread->valgrind_stackid = VALGRIND_STACK_REGISTER(newthread->ctx.uc_stack.ss_sp, newthread->ctx.uc_stack.ss_sp + newthread->ctx.uc_stack.ss_size);
  /* stocker valgrind_stackid dans votre structure thread */
  newthread->waited_by=NULL;
  if ( uc->uc_stack.ss_sp ==NULL)
    return -1;
    
  makecontext(uc, (void (*)(void))thread_call,2,(void (*)(void))func, funcarg);
  newthread->status = READY;
 
  if(initialized_library!=1){
    thread_init();

    ADD_RUNQ(newthread)
      }
  else {
    ADD_RUNQ(newthread)
      if(timer->it_interval.tv_sec ==0 && timer->it_interval.tv_usec ==0){
	timer->it_interval.tv_sec = SDELAY;
	timer->it_interval.tv_usec = USDELAY;
      }
  }
	  
  *newthreadp=newthread;   	   
  thread_yield();
  return 0;
}


/* passer la main à un autre thread.
 */
extern int thread_yield(void){
  struct thread *old_current = current;
  struct thread *t = runq_head;
 

  if(t) {
         
    // On sort t de la runq
    DELETE_FROM_RUNQ(t)
  
      // On change le statut de current (sauf si déjà terminé ou en attente)
      if(current->status==RUNNING) {
	current->status = READY;
	// On met current dans la runq
	ADD_RUNQ(current)
	  } else if(current->status==WAITING || current->status==TERMINATED) {
	// On met current dans la waitq
	ADD_WAITQ(current)
	  }
  
    // On met t dans current
    NEW_CURRENT(t)
  
      // On swap
      swapcontext(&old_current->ctx, &current->ctx);
  
    timer->it_interval.tv_sec = 0;
    timer->it_interval.tv_usec = 0;
    timer->it_value.tv_sec = 1;
    timer->it_value.tv_usec = 0;
    setitimer(ITIMER_REAL, timer, NULL);
  
  }
  else {
       // si on a aucun thread à ordonnancer on fait rien car le current doit finir son exécution
   
  
  }
  
  
  return 0;
}

/* attendre la fin d'exécution d'un thread.
 * la valeur renvoyée par le thread est placée dans *retval.
 * si retval est NULL, la valeur de retour est ignorée.
 */
extern int thread_join(struct thread * thread, void **retval){
  // Mise en attente
  // Reordonnancement (suspend)
  // if(retval != NULL) retval = thread->retval;
  // Mise en attente
  
  current->thread_wait=thread;
  current->status=WAITING;
  thread->waited_by=current;
  // On passe la main à l'ordonnanceur.

  // L'ordonnanceur nous rend la main, le retour est possible grâce à la sauvegarde précédente du contexte.
  //while(thread->status!=TERMINATED) {
  if (thread->status!=TERMINATED)
    thread_yield();
  //  }
    
  current->status=RUNNING;
  current->thread_wait=NULL;
  // Reordonnancement (suspend)
  if(retval != NULL)
    *retval = thread->retval;
  
  DELETE_FROM_WAITQ(thread)
    FREE(thread)
  
    if (!runq_head && !runq_tail)
      {
  	
  	timer->it_interval.tv_sec = 0;
	timer->it_interval.tv_usec = 0;
      }
  
  return 0;
}



/* Terminer le thread courant en renvoyant la valeur de retour retval.
 * cette fonction ne retourne jamais.
 */
extern void __attribute__ ((__noreturn__)) thread_exit(void *retval)  {
  current->status=TERMINATED;
  current->retval = retval;
  if (current->waited_by!=NULL) //si notre thread est attendu par un autre
    {
      DELETE_FROM_WAITQ(current->waited_by)
  		ADD_RUNQ(current->waited_by)
  		current->waited_by->status=READY;
    }
  thread_yield();
}

